Programas/atividades desenvolvidas para a disciplina DCA0445 - Processamento Digital de Imagens, do curso de Engenharia de Computação da Universidade Federal do Rio grande do Norte UFRN

Prefácio

Todos os programas, neste documento, foram desenvolvidas em C++ utilizando-se da biblioteca OpenCV e em ambiente Linux. Para compilar qualquer programa presente neste documento, pode-se fazer uso deste Makefile, coloca o Makefile na mesma pasta do código fonte, extensão .cpp, e execute via terminal o comando make <nome_do_programa>. Todos os códigos encontram-se no Repositório do github.

1. Programa Region

Este programa consiste em negativar uma certa região dentro de uma imagem, delimitada por um retângulo informado pelo usuário. o programa varre a área correspondente na imagem e troca os valores dos pixels para seus inversos, ou seja 255 - valor_atual.

Compilando e Executando.

$ make region
$ ./region <caminho_para_a_imagem>

O código fonte completo se encontra aqui region.cpp.

void region(Mat &img, CvPoint *p)
{
  for(unsigned int i = p[0].x; i < p[1].x; i++)
    for(unsigned int j = p[0].y; j < p[1].y; j++)
      img.at<uint8_t>(i,j) = 255 - img.at<uint8_t>(i,j);
}
toto mini
Figura 1. Entrada do programa Region
region result
Figura 2. Saída do programa Region

2. Troca Regiões

O usuário deve passar uma imagem qualquer, e o programa passara para escala de cinza e particionara a imagem em 4(quatro) partes simétricas e realizara a troca na diagonal dessas quatro subimagens.

Para este programa uma imagem foi pensada sendo composta por 4 regiões da seguinte forma:

A

B

C

D

Compilando e Executando.

$ make trocaregioes
$ ./trocaregioes <caminho_para_a_imagem>

Código completo em trocaregioes.cpp

int main(int argc, char** argv){
  Mat img;
  Mat result;
  int w, h;

  img = imread(argv[1],CV_LOAD_IMAGE_GRAYSCALE);
  //if fail to read the image
  if ( img.empty() )
  {
       cout << "Error loading the image" << endl;
       return -1;
  }

  w = img.size().width;
  h = img.size().height;
  result = img.clone();

  img(cv::Rect(0,0, w/2, h/2)).copyTo(result(cv::Rect((w-1)/2, (h-1)/2, w/2, h/2)));  (1)
  img(cv::Rect((w-1)/2, 0, w/2, h/2)).copyTo(result(cv::Rect(0, (h-1)/2, w/2, h/2))); (2)
  img(cv::Rect(0, (h-1)/2, w/2, h/2)).copyTo(result(cv::Rect((w-1)/2, 0, w/2, h/2))); (3)
  img(cv::Rect((w-1)/2, (h-1)/2, w/2, h/2)).copyTo(result(cv::Rect(0, 0, w/2, h/2))); (4)

  imshow("Original", img);
  imshow("Resultado", result);
  waitKey();

  imwrite("resultados/trocaRegiao_resultado.png", result);

  return 0;
}
1 Sobrepoe A da img original em D de result
2 Sobrepoe B da img original em C de result
3 Sobrepoe C da img original em B de result
4 Sobrepoe D da img original em A de result

Resultado

D

C

B

A

toto mini
Figura 3. Entrada do programa trocaregioes
trocaRegiao resultado
Figura 4. Saída do programa trocaregioes

3. Conta Bolhas

4. Conta Bolhas

Este programa consiste em contar o número de regiões brancas puras, com e sem "buracos", o fundo da imagem deve ser puramente preto e os objetos puramente brancos, o programa foi testado utilizando a imagem bolhas.png. Mas o mesmo deve funcionar para qualquer imagem que siga o padrão especificado a cima.

bolhas
Figura 5. Bolhas.png

O algoritmo consiste em 4 passos bem definidos. O código completo se encontra neste link: contaregioes.cpp.

4.1. Passo 1- Remover objetos das bordas

//remove da borda superior e inferior
for(int i = 0; i < width; i++){
  if(image.at<uint8_t>(0, i) == OBJ_COLOR)
    floodFill(image, CvPoint(i, 0), BACK_COLOR);
  if(image.at<uint8_t>(height-1,i) == OBJ_COLOR)
    floodFill(image, CvPoint(i, height-1), BACK_COLOR);
}

//remove das laterais
for(int i = 0; i < height; i++){
  //lateral esquerda
  if(image.at<uint8_t>(i, 0) == OBJ_COLOR)
    floodFill(image, CvPoint(0, i), BACK_COLOR);
  //lateral direita
  if(image.at<uint8_t>(i, width-1) == OBJ_COLOR)
    floodFill(image, CvPoint(width-1, i), BACK_COLOR);
}

4.2. Passo 2- Contar bolhas com buraco

//troca o background, para facilitar a identificar os buracos das bolhas
floodFill(image, CvPoint(0,0), NEW_BACK_COLOR);
for(int i = 0; i < height; i++)
  for(int j = 0; j < width; j++)
  {
    //identifica uma bolha com buraco
    if(image.at<uint8_t>(i,j) == BACK_COLOR && image.at<uint8_t>(i,j-1) == OBJ_COLOR){
      //soma um no numero de bolhas e "apaga" a bolha encontrada
      nbolhas_com_buracos++;
      floodFill(image, CvPoint(j-1, i), NEW_BACK_COLOR);
    }
  }

4.3. Passo 3- Contar bolhas sem buracos

//conta bolhas sem buracos
for(int i = 0; i < height; i++)
  for(int j = 0; j < width; j++)
  {
    //identifica uma bola
    if(image.at<uint8_t>(i,j) == OBJ_COLOR){
      //soma um no numero de bolhas e "apaga" a bolha encontrada
      nbolhas_sem_buracos++;
      floodFill(image, CvPoint(j, i), NEW_BACK_COLOR);
    }
  }

4.4. Resultado

result
Figura 6. Gif das etapas (gerado com o imagemagick)
conta bolhas resultado
Figura 7. Resultado da contagem.

5. Histograma

Breve descrição do que seja um histograma…​

5.1. Equalização de Histograma

Implementação de um Equalizador de Histograma para imagens em tons de cinza.

Algoritmo de equalização, para imagens em tons de cinza:

  1. Calcular Histograma: \(h(r_k), k \in [0,255\)];

  2. Calcular Histograma Acumulado: \(ha(r_k) = \sum{h(r_j)}, j \in [0,255\)];

  3. Normalizar o Histograma Acumulado, na faixa de [0, 255]: \(ha(r_k) = ha(r_k)/ha(r_255)\);

  4. Transformar a imagem: \(f(x,y) = ha(f(x,y))\).

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char** argv){
  Mat frame;
  Mat hist;
  VideoCapture cap;
  uint8_t histEq[256];

  int histsize = 256;
  int sum;
  float range[] = {0, 256};
  const float *histrange = { range };

  cap.open(0);

  if(!cap.isOpened()){
    cout << "cameras indisponiveis\n";
    return -1;
  }

  std::cout << "Pressione qualquer tecla para encerrar o programa." << '\n';
  while(1){
    cap >> frame;

    cvtColor(frame, frame, CV_BGR2GRAY);
    imshow("Original", frame);
    //Calculo do histograma
    calcHist(&frame, 1, 0, Mat(), hist, 1, &histsize, &histrange);


    /*calculo do histograma acumulado */
    /*e normalizacao do histograma acumulado*/
    /**
     * Calcula o vetor que ira realizar a transformacao nos valores dos pixels
     */
    sum = 0;
    for(int i = 0; i < histsize; i++)
    {
      sum+= hist.at<float>(i);
      histEq[i] = sum*255.0/frame.total();
    }

    //substituicao dos valores dos pixels
    for(int i = 0; i < frame.size().height; i++)
      for(int j = 0; j < frame.size().width; j++)
        frame.at<uint8_t>(i,j) = histEq[frame.at<uint8_t>(i,j)];
    imshow("Equalizado", frame);

    if(waitKey(10) != 255)break;
  }

  return 0;
}

Antes e Depois da equalização do histograma.

equalize input
Figura 8. Entrada do programa equalize
equalize output
Figura 9. Resultado do programa equalize

Código completo: equalize.cpp

5.2. Detector de Movimento

Utilizando comparação de histogramas entre frames consecutivos, comparando-o por calculo da correlação(usando função do OpenCV, compareHist), para identificar ocorrência de movimento, para isso foi estabelecido, de forma empírica, um limiar para a correlação, ao se identificar um valor de correlação abaixo do limiar, um circulo verde é desenhado no canto superior direito da imagem, indicando uma detecção de movimento.

Makefile utilizado para compilar o programa motiondetector, é diferente pois inclui a capacidade de gerar gifs.

Motiondetector
Figura 10. Resultado do Motiondetector.

Download do código fonte: Motiondetector.cpp.

#include <iostream>
#include <opencv2/opencv.hpp>

#include <GraphicsMagick/Magick++.h>

using namespace cv;
using namespace std;

#define NUM_FRAMES_GIF 7
#define GIF_DELAY 100 //ms

int main(int argc, char** argv){
  Magick::InitializeMagick(NULL);
  vector<Magick::Image> gifFrames(NUM_FRAMES_GIF);

  Mat frame, grayFrame;
  Mat H1, H2;
  VideoCapture cap;
  int i = 0;
  double r_correl = 0;

  int histsize = 256;
  float range[] = {0, 256};
  const float *histrange = { range };

  cap.open(0);

  if(!cap.isOpened()){
    cout << "cameras indisponiveis";
    return -1;
  }

  cap >> frame; //captura um frame
  cvtColor(frame, frame, CV_BGR2GRAY); //converte para escala de cinza
  calcHist(&frame, 1, 0, Mat(), H1, 1, &histsize, &histrange);

  while(true){
    H2 = H1.clone();
    cap >> frame; //captura um frame
    cvtColor(frame, grayFrame, CV_BGR2GRAY); //converte para escala de cinza
    calcHist(&grayFrame, 1, 0, Mat(), H1, 1, &histsize, &histrange);

    r_correl =  compareHist(H1, H2, CV_COMP_CORREL);

    if(r_correl <= 0.95)//movimento
    {
      circle(frame, Point(frame.cols - 20, 20), 10, Scalar(0, 255, 0), CV_FILLED);
    }else{
      circle(frame, Point(frame.cols - 20, 20), 10, Scalar(255, 255 ,255), CV_FILLED);
    }

    imshow("Live", frame);

    //Salva o frame para o array de frames que sera usado para gerar o gif
    gifFrames[i] = Magick::Image(frame.cols,
                                 frame.rows, "BGR",
                                 Magick::StorageType::CharPixel,
                                (uint8_t*)frame.data);
    gifFrames[i].animationDelay(GIF_DELAY);
    i = (i+1)%NUM_FRAMES_GIF; //para simular uma fila circular com o vector

    if(waitKey(30) != 255)break;
  }

  //Gera o gif, colocando-o no arquivo "saidaKmeans.gif"
  Magick::writeImages(gifFrames.begin(), gifFrames.end(), "Motiondetector.gif");
  return 0;
}

6. Filtros 2D

Explicação

6.1. Laplaciano do Gaussiano (Lapgauss)

Filter2D
Figura 11. Resultado do lapgauss.cpp

código completo: lapgauss.cpp

#include <iostream>
#include <opencv2/opencv.hpp>
#include <GraphicsMagick/Magick++.h>

using namespace cv;
using namespace std;

#define MAX_FILTER 2

#define NUM_FRAMES_GIF 100
#define GIF_DELAY 10 //ms

void printmask(Mat &m){
  for(int i=0; i<m.size().height; i++){
    for(int j=0; j<m.size().width; j++){
      cout << m.at<float>(i,j) << ",";
    }
    cout << endl;
  }
}

void menu(){
  cout << "\npressione a tecla para ativar o filtro: \n"
	         "a - calcular modulo\n"
           "m - media\n"
           "g - gauss\n"
           "v - vertical\n"
	         "h - horizontal\n"
           "l - laplaciano\n"
           "i - identidade\n"
           "e - laplaciano do Gaussiano\n"
	         "esc - sair\n";
}

int main(int argvc, char** argv){
  Magick::InitializeMagick(NULL);
  vector<Magick::Image> gifFrames(NUM_FRAMES_GIF);
  int k = 0;

  VideoCapture video;
  float identidade[] = {0, 0, 0,
                        0, 1, 0,
                        0, 0, 0};
  float media[] = {1,1,1,
				           1,1,1,
				           1,1,1};
  float gauss[] = {1,2,1,
				           2,4,2,
				           1,2,1};
  float horizontal[]={-1,0,1,
					            -2,0,2,
					            -1,0,1};
  float vertical[]={-1,-2,-1,
					           0, 0, 0,
					           1, 2, 1};
  float laplacian[]={0,-1,0,
					          -1,4,-1,
					           0,-1,0};

  Mat cap, frame, frame32f, frameFiltered;
  Mat mask[] = { Mat(3,3, CV_32F), Mat(3,3, CV_32F) };
  Mat maskAux;
  Mat result;
  char* text = "Media";
  double width, height;
  int absolut;
  char key;

  video.open(0);
  if(!video.isOpened())
    return -1;
  width=video.get(CV_CAP_PROP_FRAME_WIDTH);
  height=video.get(CV_CAP_PROP_FRAME_HEIGHT);
  std::cout << "largura=" << width << "\n";;
  std::cout << "altura =" << height<< "\n";;

  namedWindow("filtroespacial",1);

  mask[0] = Mat(3, 3, CV_32F, media);
  scaleAdd(mask[0], 1/9.0, Mat::zeros(3,3,CV_32F), maskAux);
  mask[0] = maskAux.clone();
  absolut = 1; // calcs abs of the image
  mask[1] = Mat(3, 3, CV_32F, identidade);

  menu();
  for(;;){
    video >> cap;
    cvtColor(cap, frame, CV_BGR2GRAY);//converte a imagem para tons de cinza
    flip(frame, frame, 1); //espelha a imagem

    imshow("original", frame);//exibe a imagem

    frame.convertTo(frameFiltered, CV_32F); //cria uma imagem tipo float
    //aplicacao dos filtros, seguindo uma ordem, da mascara[0] ate mascara[i]
    //fazendo com que os efeitos das filtragens sejam cascateados
    for(int i = 0; i < MAX_FILTER; i++)
    {
      filter2D(frameFiltered, frameFiltered, frameFiltered.depth(), mask[i], Point(1,1), 0); //aplica o filtro 1
      if(absolut)frameFiltered = abs(frameFiltered);
    }

    frameFiltered.convertTo(result, CV_8U); //converte a imagem filtrada de float para byte em tons de cinza
    putText(result, text, Point(5,50), FONT_HERSHEY_DUPLEX, 1, Scalar(255), 2);

    imshow("filtroespacial", result);//exibe a imagem filtrada


    key = (char) waitKey(10);
    if( key == 27 ) break; // esc pressed!
    switch(key){
    case 'a':
	  menu();
      absolut=!absolut;
      break;
    case 'm':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, media);
      scaleAdd(mask[0], 1/9.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Media";



      break;
    case 'g':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, gauss);
      scaleAdd(mask[0], 1/16.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Gaussiano";
      break;
    case 'h':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, horizontal);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Horizontal";
      break;
    case 'v':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, vertical);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Vertical";
      break;
    case 'l':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, laplacian);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Laplaciano";
      break;
    case 'i':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, identidade);
      mask[1] = Mat(3, 3, CV_32F, identidade);
      text = "Identidade";
      break;
    case 'e':
	  menu();
      mask[0] = Mat(3, 3, CV_32F, gauss);
      scaleAdd(mask[0], 1/16.0, Mat::zeros(3,3,CV_32F), maskAux);
      mask[0] = maskAux.clone();
      mask[1] = Mat(3, 3, CV_32F, laplacian);
      text = "Laplaciano do Gaussiano";
      break;
    default:
      break;
    }


    for(int i = 0; i < height; i++)
      for(int j = 0; j < width; j++)
        result.at<uint8_t>(i,j) = 255 - result.at<uint8_t>(i,j);

    gifFrames[k] = Magick::Image(result.cols,
                                 result.rows, "K",
                                 Magick::StorageType::CharPixel,
                                (uint8_t*)result.data);

    gifFrames[k].animationDelay(GIF_DELAY);
    k = (k+1)%NUM_FRAMES_GIF; //para simular uma fila circular com o vector
  }
  //Gera o gif, colocando-o no arquivo "saidaKmeans.gif"
  Magick::writeImages(gifFrames.begin(), gifFrames.end(), "Filter2D.gif");
  return 0;
}

6.2. Tilt-Shift

Demonstração Tiltshift

Código completo: tiltshift.cpp.

#include <iostream>
#include <math.h>       /* tanh, log */
#include <opencv2/opencv.hpp>

using namespace cv;

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring);

void slot_sliders(int, void*);
double alfa_func(int x, int l1, int l2, double d);

Mat image, blender;
int decay_int = 50;
int center_int = 50;
int c_width_int = 50;
int blurring_int = 50;
const char mainWindow[] = "TiltSfhit";


int main(int argc, char** argv){
  const int max_slider = 100;

  if(argc != 1){//usuario passou um argumento, vou entender como sendo um endereco de uma imagem
    image = imread(argv[1]);
  }else{
    image = imread("imagens/toto_bola_cores.png");
  }

  if(image.data == NULL){
    std::cerr << "Erro ao abrir a Imagem!!" << '\n';
    return 1;
  }

  namedWindow(mainWindow, WINDOW_AUTOSIZE);
  imshow(mainWindow, image);

  createTrackbar( "Central Width [0,100]", mainWindow,
                  &c_width_int,
                  max_slider,
                  slot_sliders);

  createTrackbar( "Center [0,100]", "TiltSfhit",
        				  &center_int,
        				  max_slider,
        				  slot_sliders);

  createTrackbar( "Decay [0,100]", mainWindow,
        				  &decay_int,
        				  max_slider,
        				  slot_sliders);

  createTrackbar( "Blurring [0,100]", mainWindow,
                  &blurring_int,
                  max_slider,
                  slot_sliders);

  slot_sliders(0,0);

  waitKey(0);
  return 0;
}

void slot_sliders(int, void*){
  tiltShift(image, blender, c_width_int/100.0, decay_int/100.0, center_int/100.0, blurring_int/100.0);
  imshow(mainWindow, blender);
};

double alfa_func(int x, int l1, int l2, double d){
  return 0.5*( tanh((x-l1)/d) - tanh((x-l2)/d) );
}

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring)
{
  if(delta_l > 1 || center > 1 || decay > 1 || blurring > 1)return true;
  if(delta_l < 0 || center < 0 || decay < 0 || blurring < 0)return true;
  if(image_src.data == NULL)return true;

  const static int intensity_max = 100;
  Mat blurry_img;
  const int x_max = image_src.rows;
  Mat mask, image_32f;
  float weigth = 1/18.0;
  float average[] = {2, 2, 2, 2, 2, 2, 2, 2, 2};


  mask = Mat(3, 3, CV_32F, average);
  scaleAdd(mask, weigth, Mat::zeros(3,3,CV_32F), mask);
  image_src.convertTo(image_32f, CV_32F);
  for(int i = 0; i < intensity_max*blurring; i++)
  {
    filter2D(image_32f, image_32f, image_32f.depth(), mask, Point(1,1));
    image_32f = abs(image_32f);
  }
  image_32f.convertTo(blurry_img, CV_8U);

  #define L1 (center - delta_l/2.0)*x_max
  #define L2 (center + delta_l/2.0)*x_max
  image_dest = image_src.clone();
  double alfa;
  for(int x = 0; x < x_max; x++)
  {
      alfa = alfa_func(x, L1, L2, decay);
      addWeighted(  image_src.row(x),
                    alfa,
                    blurry_img.row(x),
                    1-alfa,
                    0.0,
                    image_dest.row(x));
  }

  return false;
};

6.3. Tilt-Shift em Vídeo

Demonstração Tiltshift em vídeo

Código completo em: tiltshift_video.cpp.

#include <iostream>
#include <math.h>       /* tanh, log */
#include <opencv2/opencv.hpp>

using namespace cv;

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring);//0 to 1

int main(int argc, char** argv){
  VideoCapture video_src;
  Mat frame;

  video_src.open(argv[1]);
  if(video_src.isOpened() == false){
    std::cerr << "Erro ao carregar arquivo de video!" << '\n';
    return 1;
  }

  int frame_width = static_cast<int>(video_src.get(CAP_PROP_FRAME_WIDTH));
  int frame_height = static_cast<int>(video_src.get(CAP_PROP_FRAME_HEIGHT));
  Size frame_size(frame_width, frame_height);
  int frames_per_second = 3;
  int steps = 10;

  //Create and initialize the VideoWriter object
  VideoWriter video_result("resultados/tiltshift_video.avi", VideoWriter::fourcc('X','V','I','D'),
                                                             frames_per_second, frame_size, true);
  if(video_result.isOpened() == false){
   std::cerr << "Erro ao carregar arquivo de video!" << '\n';
   return 1;
  }

  std::cout << "Processando..." << '\n';

  double alpha = 1.6;
  int beta = 1;

  while(true){
    video_src >> frame;
    if(frame.data == NULL)break;

    tiltShift(frame, frame, 0.3, 0.0001, 0.4, 0.15);

    for( int y = 0; y < frame.rows; y++ ) {
        for( int x = 0; x < frame.cols; x++ ) {
            for( int c = 0; c < 3; c++ ) {
                frame.at<Vec3b>(y,x)[c] =
                saturate_cast<uchar>( alpha*( frame.at<Vec3b>(y,x)[c] ) + beta );
            }
        }
    }

    video_result.write(frame);

    int i;
    for(i = 0; i < steps; i++)
    {
      if(frame.data == NULL)break; //final do arquivo
      video_src >> frame;
    }
    if(i != steps)break;
  }

  video_src.release();
  video_result.release();

  std::cout << "Terminou!" << '\n';

  system("vlc resultados/tiltshift_video.avi");

  return 0;
}


double alfa_func(int x, int l1, int l2, double d){
  return 0.5*( tanh((x-l1)/d) - tanh((x-l2)/d) );
}

bool tiltShift(Mat &image_src,
               Mat &image_dest,
               float delta_l, //0 to 1
               float decay,   //0 to 1
               float center,  //0 to 1
               float blurring)
{
  if(delta_l > 1 || center > 1 || decay > 1 || blurring > 1)return true;
  if(delta_l < 0 || center < 0 || decay < 0 || blurring < 0)return true;
  if(image_src.data == NULL)return true;

  const static int intensity_max = 100;
  Mat blurry_img;
  const int x_max = image_src.rows;
  Mat mask, image_32f;
  float weigth = 1/9.0;
  float average[] = {1, 1, 1, 1, 1, 1, 1, 1, 1};


  mask = Mat(3, 3, CV_32F, average);
  scaleAdd(mask, weigth, Mat::zeros(3,3,CV_32F), mask);
  image_src.convertTo(image_32f, CV_32F);
  for(int i = 0; i < intensity_max*blurring; i++)
  {
    filter2D(image_32f, image_32f, image_32f.depth(), mask, Point(1,1));
    image_32f = abs(image_32f);
  }
  image_32f.convertTo(blurry_img, CV_8U);

  #define L1 (center - delta_l/2.0)*x_max
  #define L2 (center + delta_l/2.0)*x_max
  image_dest = image_src.clone();
  double alfa;
  for(int x = 0; x < x_max; x++)
  {
      alfa = alfa_func(x, L1, L2, decay);
      addWeighted(  image_src.row(x),
                    alfa,
                    blurry_img.row(x),
                    1-alfa,
                    0.0,
                    image_dest.row(x));
  }

  return false;
};

7. Bibliografia

  • Rafael Gonzalez. 'Processamento Digital de Imagens'. Addison-Wesley. 1990. 2 ed.